/**
* Copyright @ 2023 iAuto (Shanghai) Co., Ltd.
* All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are NOT permitted except as agreed by
* iAuto (Shanghai) Co., Ltd.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/
//
/*eslint-disable*/
import Blockly from '@scene/scene-blocks';
import throttle from 'lodash/throttle'

// 控制ifelse 底部距离
Blockly.BlockSvg.IF_ELSE_BOTTOM = 4 * Blockly.BlockSvg.GRID_UNIT
/*
 * NOTCH_PATH_LEFT
 * 从左到右绘制下一个/上一个缺口的 SVG 路径
 * 上方凹槽
*/
Blockly.BlockSvg.NOTCH_PATH_LEFT = 'l 36,0'

/* 
* 从右到左绘制下一个/上一个缺口的 SVG 路径
* 下方凸块
*/
Blockly.BlockSvg.NOTCH_PATH_RIGHT = 'l -36,0'

/**
 * hat曲线的路径
 * @const
 */
Blockly.BlockSvg.START_HAT_PATH ='m 0,' + 5 * Blockly.BlockSvg.CORNER_RADIUS +
    'A ' + 
    5 * Blockly.BlockSvg.CORNER_RADIUS + ',' +
    5 * Blockly.BlockSvg.CORNER_RADIUS + ' 0 0,1 ' +
    5 * Blockly.BlockSvg.CORNER_RADIUS + ',0';

// 分类积木
Blockly.BlockSvg.EXCEPT_OPERATE = ["control_if", "control_if_else", "control_when", "control_loop", "control_exit"]
// 绘制logo的积木
Blockly.BlockSvg.RENDER_LOGO = ["control_if", "control_if_else", "control_loop", "control_exit"]
// 积木分类 循环和退出积木 在拼接是位置的控制
Blockly.BlockSvg.LOOP_AND_EXIT = ["control_loop", "control_exit"]

// 积木互斥的提前判断 减少判断次数
Blockly.BlockSvg.LIMIT_BLOCK = ["control_if", "control_if_else", "control_loop", "control_exit"]

// 积木限制配置
Blockly.BlockSvg.LIMIT_BLOCKOBJ = {
    control_if: ["control_if", "control_if_else", "control_exit"],
    control_if_else: ["control_if", "control_if_else", "control_exit"],
    control_loop: ["control_loop", "control_exit"],
    control_exit: ["control_exit", "control_if", "control_if_else"]
}

// 包裹积木的积木
Blockly.BlockSvg.SURROUND_BLOCK = ["control_if", "control_if_else", "control_loop", "control_exit"]

// 禁止右键出现删除菜单
Blockly.BlockSvg.NO_CONTEXT_MENU_DELETE = ['control_when', 'control_pre_conditions'];


/**
 * Render the right edge of the block.
 * @param {!Array.<string>} steps Path of block outline.
 * @param {!Array.<!Array.<!Object>>} inputRows 2D array of objects, each
 *     containing position information.
 * @param {number} iconWidth Offset of first row due to icons.
 * @return {number} Height of block.
 * @private
 * 渲染块的右边缘。
 * @param {!Array.<string>} 步骤块轮廓的路径。
 * @param {!Array.<!Array.<!Object>>} inputRows 2D 对象数组，每个
 *     包含位置信息。
 * @param {number} iconWidth 由于图标而导致的第一行偏移。
 * @return {number} 块的高度。
 * 
 */
Blockly.BlockSvg.prototype.renderDrawRight_ = function(steps,inputRows, iconWidth) {
  var cursorX = 0;
  var cursorY = 0;
  var connectionX, connectionY;
   // eslint-disable-next-line
  for (var y = 0, row; row = inputRows[y]; y++) {
    cursorX = row.paddingStart;
    if (y == 0) {
      cursorX += this.RTL ? -iconWidth : iconWidth;
    }

    if (row.type == Blockly.BlockSvg.INLINE) {
      // Inline inputs.
       // eslint-disable-next-line
      for (var x = 0, input; input = row[x]; x++) {
        // Align fields vertically within the row.
        // Moves the field to half of the row's height.
        // In renderFields_, the field is further centered
        // by its own rendered height.
        // 在行内垂直对齐字段。
        // 将字段移动到行高的一半。
        // 在 renderFields_ 中，该字段进一步居中
        // 通过它自己的渲染高度。
        var fieldY = cursorY + row.height / 2;

        var fieldX = Blockly.BlockSvg.getAlignedCursor_(cursorX, input,inputRows.rightEdge);

        cursorX = this.renderFields_(input.fieldRow, fieldX, fieldY);
        if (input.type == Blockly.INPUT_VALUE) {
          // Create inline input connection.
          // In blocks with a notch, inputs should be bumped to a min X,
          // to avoid overlapping with the notch.
          // 创建内联输入连接。
          // 在带有缺口的块中，输入应该被撞到最小 X，
          // 以避免与缺口重叠。

          /* ********* 改动区域 ********** */
          /* 绘制if else 内部链接形状的间隙 距离左侧的距离 */
          
          /* **************************** */
          if (this.previousConnection) {
            cursorX = Math.max(cursorX, Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X);
          }
          connectionX = this.RTL ? -cursorX : cursorX;
          // Attempt to center the connection vertically.
          var connectionYOffset = row.height / 2;
          connectionY = cursorY + connectionYOffset;
          input.connection.setOffsetInBlock(connectionX, connectionY);
          this.renderInputShape_(input, cursorX, cursorY + connectionYOffset);
          cursorX += input.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
        }
      }
      // Remove final separator and replace it with right-padding.
      cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
      cursorX += row.paddingEnd;
      // Update right edge for all inputs, such that all rows
      // stretch to be at least the size of all previous rows.
      // 更新所有输入的右边缘，使得所有行
      // 拉伸到至少所有先前行的大小。
      inputRows.rightEdge = Math.max(cursorX, inputRows.rightEdge);
      // Move to the right edge
      cursorX = Math.max(cursorX, inputRows.rightEdge);
      this.width = Math.max(this.width, cursorX);
      if (!this.edgeShape_) {
        // Include corner radius in drawing the horizontal line.
        steps.push('H', cursorX - Blockly.BlockSvg.CORNER_RADIUS - this.edgeShapeWidth_);
        steps.push(Blockly.BlockSvg.TOP_RIGHT_CORNER);
      } else {
        /* ********* 改动区域 ********** */
        /* 改动条件框形状 */
        // 条件框 形状更改 左侧闭合
        // Don't include corner radius - no corner (edge shape drawn).
        steps.push('H', cursorX - this.edgeShapeWidth_);
        /* ************************** */
      }
      // Subtract CORNER_RADIUS * 2 to account for the top right corner
      // and also the bottom right corner. Only move vertically the non-corner length.
      // 减去 CORNER_RADIUS *2 占右上角
      // 还有右下角。仅垂直移动非角长度。
      if (!this.edgeShape_) {
          steps.push('v', row.height - Blockly.BlockSvg.CORNER_RADIUS * 2);
      }
    } else if (row.type == Blockly.NEXT_STATEMENT) {
      // Nested statement.
       // eslint-disable-next-line
      var input = row[0];
       // eslint-disable-next-line
      var fieldX = cursorX;
      // Align fields vertically within the row.
      // In renderFields_, the field is further centered by its own height.
      // 在行内垂直对齐字段。
      // 在 renderFields_ 中，该字段以其自身的高度为中心。
       // eslint-disable-next-line
      var fieldY = cursorY;
      // eg 如果  那么   那么字段绘制
      
      /* ********* 改动区域 ********** */
      /* 
      *  积木如果那么字段绘制  绘制字段的高度位置
      */
      fieldY += Blockly.BlockSvg.MIN_STATEMENT_INPUT_HEIGHT;
      /* *************************** */
      this.renderFields_(input.fieldRow, fieldX, fieldY);
      // Move to the start of the notch.
      cursorX = inputRows.statementEdge + Blockly.BlockSvg.NOTCH_WIDTH;

      if (this.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) {
        this.renderDefineBlock_(steps, inputRows, input, row, cursorY);
      } else {
        Blockly.BlockSvg.drawStatementInputFromTopRight_(steps, cursorX,inputRows.rightEdge, row, this.type);
      }

      // Create statement connection.
      /* ********* 改动区域 ********** */
      /* 
      * 按积木分类 给积木补偿链接位置
       */
        // 积木链接时 链接出要少2个单位 Blockly.BlockSvg.CORNER_RADIUS
        
      connectionX = this.RTL ? -cursorX : cursorX ;
      /* *************************** */
      input.connection.setOffsetInBlock(connectionX, cursorY);
      if (input.connection.isConnected()) {
        this.width = Math.max(this.width, inputRows.statementEdge +
          input.connection.targetBlock().getHeightWidth().width);
      }
      if (this.type != Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE &&
        (y == inputRows.length - 1 ||
          inputRows[y + 1].type == Blockly.NEXT_STATEMENT)) {
        // If the final input is a statement stack, add a small row underneath.
        // Consecutive statement stacks are also separated by a small divider.
        // 如果最终输入是语句堆栈，请在下方添加一小行。
        // 连续的语句堆栈也由一个小分隔符分隔。
        steps.push(Blockly.BlockSvg.TOP_RIGHT_CORNER);
        /* ********* 改动区域 ********** */
        /* 改动 if else 底部模型积木厚度位置 */
        
        steps.push('v', Blockly.BlockSvg.EXTRA_STATEMENT_ROW_Y - 6 * Blockly.BlockSvg.CORNER_RADIUS);
        cursorY += Blockly.BlockSvg.EXTRA_STATEMENT_ROW_Y - 4 * Blockly.BlockSvg.CORNER_RADIUS;
        /* ******************************** */
      }
    }
    cursorY += row.height;
  }
  this.drawEdgeShapeRight_(steps);
  if (!inputRows.length) {
    cursorY = Blockly.BlockSvg.MIN_BLOCK_Y;
    steps.push('V', cursorY);
  }
  
  return cursorY;
};

/**
 * Render the bottom edge of the block.
 * @param {!Array.<string>} steps Path of block outline.
 * @param {number} cursorY Height of block.
 * @private.
 * * 渲染块的底部边缘。
 * @param {!Array.<string>} 步骤块轮廓的路径。
 * @param {number} cursorY 块的高度。
 */
Blockly.BlockSvg.prototype.renderDrawBottom_ = function(steps, cursorY) {
  this.height = cursorY;
  
  if (!this.edgeShape_) {
    steps.push(Blockly.BlockSvg.BOTTOM_RIGHT_CORNER);
  }
  if (this.nextConnection) {
    // Move to the right-side of the notch.
    var notchStart = (
      Blockly.BlockSvg.NOTCH_WIDTH +
      Blockly.BlockSvg.NOTCH_START_PADDING +
      Blockly.BlockSvg.CORNER_RADIUS
    );
    steps.push('H', notchStart, ' ');
    steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT);
    // Create next block connection.
    // 创建下一个块连接。
    var connectionX = this.RTL ? -Blockly.BlockSvg.NOTCH_WIDTH :
        Blockly.BlockSvg.NOTCH_WIDTH;
    this.nextConnection.setOffsetInBlock(connectionX, cursorY);
    // Include height of notch in block height.
    // 在块高度中包括槽口高度。
    this.height += Blockly.BlockSvg.NOTCH_HEIGHT;
  }
  // Bottom horizontal line
  if (!this.edgeShape_) {
    steps.push('H', Blockly.BlockSvg.CORNER_RADIUS);
    // Bottom left corner
    steps.push(Blockly.BlockSvg.BOTTOM_LEFT_CORNER);
  /* ********* 改动区域 ********** */
  /*
   *  条件框 的底边绘制
   */
  
  /* *************************** */
  } else  {
    steps.push('H', this.edgeShapeWidth_);
  }
};

/**
 * Render the left edge of the block.
 * @param {!Array.<string>} steps Path of block outline.
 * @param {number} cursorY Height of block.
 * @private
 * 渲染块的左边缘。
 * @param {!Array.<string>} 步骤块轮廓的路径。
 * @param {number} cursorY 块的高度。
 */
Blockly.BlockSvg.prototype.renderDrawLeft_ = function(steps) {
  if (this.outputConnection) {
    // Scratch-style reporters have output connection y at half block height.
    this.outputConnection.setOffsetInBlock(0, this.height / 2);
  }
  if (this.edgeShape_) {
    // Draw the left-side edge shape.
    if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_ROUND) {
      // Draw a rounded arc.
      steps.push('a ' + this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_ + ' 0 0 1 0 -' + this.edgeShapeWidth_ * 2);
    } else if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_HEXAGONAL) {
      /* ************ 改动区域 ***************** */
      /*
       * 绘制体检框左半边
       */
      // Draw a half-hexagon.
      
      steps.push('l ' + -this.edgeShapeWidth_ + ' ' + -this.edgeShapeWidth_ +
        ' l ' + this.edgeShapeWidth_ + ' ' + -this.edgeShapeWidth_);
      /* ************************************** */
    }
  }
  steps.push('z');
};

/**
 * Draw the edge shape (rounded or hexagonal) on the right side of a block with
 * an output.
 * @param {!Array.<string>} steps Path of block outline.
 * @private
 * 在块的右侧绘制边缘形状（圆形或六边形）
 * 一个输出。
 * @param {!Array.<string>} 步骤块轮廓的路径。
 */
Blockly.BlockSvg.prototype.drawEdgeShapeRight_ = function(steps) {
  if (this.edgeShape_) {
    // Draw the right-side edge shape.
    if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_ROUND) {
      // Draw a rounded arc.
      steps.push('a ' + this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_ +
          ' 0 0 1 0 ' + this.edgeShapeWidth_ * 2);
    } else if (this.edgeShape_ === Blockly.OUTPUT_SHAPE_HEXAGONAL) {
      /* ************ 改动区域 ***************** */
      /*
       * 绘制体检框右半边
       */
      // Draw an half-hexagon.
     
      steps.push('l ' + this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_ +
          ' l ' + -this.edgeShapeWidth_ + ' ' + this.edgeShapeWidth_);
      /* ************************************** */      
    }
  }
};

/**
 * Draw the top of the outline of a statement input, starting at the top right
 * corner.
 * @param {!Array.<string>} steps Path of block outline.
 * @param {number} cursorX The x position of the start of the notch at the top
 *     of the input.
 * @private
 * 绘制语句输入轮廓的顶部，从右上角开始
 * 角落。
 * @param {!Array.<string>} 步骤块轮廓的路径。
 * @param {number} cursorX 顶部凹槽开始的 x 位置
 *     的输入。
 */
Blockly.BlockSvg.drawStatementInputTop_ = function(steps, cursorX) {
  steps.push(Blockly.BlockSvg.BOTTOM_RIGHT_CORNER);

  /* ************ 改动区域 ***************** */
  // 10 * Blockly.BlockSvg.CORNER_RADIUS 数值控制 条件框左侧厚度
  
  steps.push('H', cursorX + Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE +
    2 * Blockly.BlockSvg.CORNER_RADIUS);
  /* ************************************ */
  steps.push(Blockly.BlockSvg.NOTCH_PATH_RIGHT);
  steps.push('h', '-' + Blockly.BlockSvg.STATEMENT_INPUT_INNER_SPACE);
  steps.push(Blockly.BlockSvg.INNER_TOP_LEFT_CORNER);
};

/**
 * Render a list of fields starting at the specified location.
 * @param {!Array.<!Blockly.Field>} fieldList List of fields.
 * @param {number} cursorX X-coordinate to start the fields.
 * @param {number} cursorY Y-coordinate around which fields are centered.
 * @return {number} X-coordinate of the end of the field row (plus a gap).
 * @private
 *  呈现从指定位置开始的字段列表。
 * @param {!Array.<!Blockly.Field>} fieldList 字段列表。
 * @param {number} cursorX X 坐标开始字段。
 * @param {number} cursorY 以字段为中心的 Y 坐标。
 * @return {number} 字段行末尾的 X 坐标（加上一个间隙）。
 * @私人的
 */
// 定位字体位置
Blockly.BlockSvg.prototype.renderFields_ = function(fieldList, cursorX, cursorY) {
  if (this.RTL) {
    cursorX = -cursorX;
  }
   // eslint-disable-next-line
  for (var t = 0, field; field = fieldList[t]; t++) {
    var root = field.getSvgRoot();
    if (!root) {
      continue;
    }
    // In blocks with a notch, fields should be bumped to a min X,
    // to avoid overlapping with the notch. Label and image fields are
    // excluded.
    // 在带有缺口的块中，字段应该被撞到最小 X，
    // 以避免与缺口重叠。标签和图像字段是排除。
    if (this.previousConnection && !(field instanceof Blockly.FieldLabel) &&
        !(field instanceof Blockly.FieldImage)) {
      cursorX = this.RTL ?
        Math.min(cursorX, -Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X) :
        Math.max(cursorX, Blockly.BlockSvg.INPUT_AND_FIELD_MIN_X);
    }
    // Offset the field upward by half its height.
    // This vertically centers the fields around cursorY.
    // 将场向上偏移其高度的一半。
    // 这将围绕 cursorY 垂直居中。
    var yOffset = -field.getSize().height / 2;

    // If this is an extension block, and this field is the first field, and
    // it is an image field, and this block has a previous connection, bump
    // the image down by one grid unit to align it vertically.
    // 如果这是一个扩展块，并且该字段是第一个字段，并且
    // 它是一个图像字段，并且这个块有一个先前的连接，bump
    // 图像向下一个网格单元以垂直对齐。
    if (this.isScratchExtension && (field === this.inputList[0].fieldRow[0])
        && (field instanceof Blockly.FieldImage) && this.previousConnection) {
        yOffset += Blockly.BlockSvg.GRID_UNIT;
    }

    // If this is an extension hat block, adjust the height of the vertical
    // separator without adjusting the field height. The effect is to move
    // the bottom end of the line up one grid unit.
    // 如果这是一个扩展帽块，调整垂直高度
    // 分隔符而不调整字段高度。效果是移动
    // 行的底端向上一个网格单元。
    if (this.isScratchExtension &&
        !this.previousConnection && this.nextConnection &&
        field instanceof Blockly.FieldVerticalSeparator) {
      field.setLineHeight(Blockly.BlockSvg.ICON_SEPARATOR_HEIGHT - Blockly.BlockSvg.GRID_UNIT);
    }
    /* ************* 改动区域 ***************** */
    //  logo 绘制偏移
    if (field instanceof Blockly.FieldImage && Blockly.BlockSvg.RENDER_LOGO.includes(this.type)) {
        yOffset = yOffset - 30;
    }
    /* ************************************** */
    var translateX, translateY;
    var scale = '';
    if (this.RTL) {
      cursorX -= field.renderSep + field.renderWidth;
      translateX = cursorX;
      translateY = cursorY + yOffset;
      if (field.renderWidth) {
        cursorX -= Blockly.BlockSvg.SEP_SPACE_X;
      }
    } else {
      translateX = cursorX + field.renderSep;
      translateY = cursorY + yOffset;
      if (field.renderWidth) {
        cursorX += field.renderSep + field.renderWidth + Blockly.BlockSvg.SEP_SPACE_X;
      }
    }
    if (this.RTL &&
        field instanceof Blockly.FieldImage &&
        field.getFlipRTL()) {
      scale = 'scale(-1 1)';
      translateX += field.renderWidth;
    }
    root.setAttribute('transform', 'translate(' + translateX + ', ' + translateY + ') ' + scale);

    // Fields are invisible on insertion marker.
    // 字段在插入标记上不可见。
    if (this.isInsertionMarker()) {
      root.setAttribute('display', 'none');
    }
  }
  return this.RTL ? -cursorX : cursorX;
};

/**
 * Update this gesture to record whether a block is being dragged.
 * This function should be called on a mouse/touch move event the first time the
 * drag radius is exceeded.  It should be called no more than once per gesture.
 * If a block should be dragged, either from the flyout or in the workspace,
 * this function creates the necessary BlockDragger and starts the drag.
 * @return {boolean} true if a block is being dragged.
 * @private
 */
Blockly.Gesture.prototype.updateIsDraggingBlock_ = function() {
  if (!this.targetBlock_) {
    return false;
  }
  if(this.targetBlock_ != null && this.targetBlock_?.type =="control_pre_conditions" ) {
    return false;
  }
  

  if (this.flyout_) {
    this.isDraggingBlock_ = this.updateIsDraggingFromFlyout_();
  } else if (this.targetBlock_.isMovable() || this.shouldDuplicateOnDrag_){
    this.isDraggingBlock_ = true;
  }

  if (this.isDraggingBlock_) {
    this.startDraggingBlock_();
    return true;
  }
  return false;
};



Blockly.InsertionMarkerManager.prototype.shouldUpdatePreviews_ = function(candidate, dxy) {
  var candidateLocal = candidate.local;
  var candidateClosest = candidate.closest;
  var radius = candidate.radius;
  
  // Found a connection!
  if (candidateLocal && candidateClosest) {
    if (candidateClosest.sourceBlock_.type =='control_when' && !candidateLocal?.check_?.includes("Boolean")) {
      return false
    }
    if (candidateClosest.sourceBlock_.type =='control_pre_conditions' && candidateClosest.type == 4) {
      return false
    }


    if (candidateLocal.type == Blockly.OUTPUT_VALUE) {
      // Always update previews for output connections.
      return true;
    }
    // We're already showing an insertion marker.
    // Decide whether the new connection has higher priority.
    if (this.localConnection_ && this.closestConnection_) {
      // The connection was the same as the current connection.
      if (this.closestConnection_ == candidateClosest) {
        return false;
      }
      var xDiff = this.localConnection_.x_ + dxy.x - this.closestConnection_.x_;
      var yDiff = this.localConnection_.y_ + dxy.y - this.closestConnection_.y_;
      var curDistance = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
      // Slightly prefer the existing preview over a new preview.
      return  !(candidateClosest && radius > curDistance -
          Blockly.CURRENT_CONNECTION_PREFERENCE);
    } else if (!this.localConnection_ && !this.closestConnection_) {
    // We weren't showing a preview before, but we should now.
      return true;
    } else {
      console.error('Only one of localConnection_ and closestConnection_ was set.');
    }
  } else { // No connection found.
    // Only need to update if we were showing a preview before.
    return !!(this.localConnection_ && this.closestConnection_);
  }

  console.error('Returning true from shouldUpdatePreviews, but it\'s not clear why.');
  return true;
};


/**
 * Connect to the closest connection and render the results.
 * This should be called at the end of a drag.
 * @package
 */
Blockly.InsertionMarkerManager.prototype.applyConnections = function() {
  if (this.closestConnection_) {
    // Don't fire events for insertion markers.
    Blockly.Events.disable();
    this.hidePreview_();
    Blockly.Events.enable();
    // Connect two blocks together.
    this.localConnection_.connect(this.closestConnection_);
    if (this.topBlock_.rendered) {
      // Trigger a connection animation.
      // Determine which connection is inferior (lower in the source stack).
      var inferiorConnection = this.localConnection_.isSuperior() ?
          this.closestConnection_ : this.localConnection_;
      Blockly.BlockAnimations.connectionUiEffect(
          inferiorConnection.getSourceBlock());
      // Bring the just-edited stack to the front.
      var rootBlock = this.topBlock_.getRootBlock();
      rootBlock.bringToFront();
    }
    // console.log("🚀 ~ file: insertion_marker_manager.js ~ line 226 ~ this.localConnection_", this.localConnection_)
    // console.log("🚀 ~ file: insertion_marker_manager.js ~ line 227 ~ this.closestConnection_", this.closestConnection_)

    /* ************ 改动区域 ***************** */
    /* 全新增
     * 限制嵌套逻辑
     */

    // 拖动积木
    const local = this.localConnection_.sourceBlock_
    // 目标积木
    const closest = this.closestConnection_.sourceBlock_
    
    // 判断塞入体检框条件 
    if(local.category_ == "operators" && (local.type == "operator_or" || local.type == "operator_and")) {
      // 统计拖动的积木嵌套了几层条件
      const localNum = this.localTermNums(local)
      // 统计塞入的目标积木有几层条件
      let closestNum
      if(closest.type == "operator_or" || closest.type == "operator_and") {
        closestNum = this.termNums(closest)
      } else {
        closestNum = this.termNums(closest, 0)
      }
      const level = localNum + closestNum
      if (level > 3) {
        setTimeout(function() {
          Blockly.mainWorkspace.undo(false)
        });
      }
    }

    // 子积木数组 
    let localTypes;
    // 限制块类型数组
    let parentsLimitTypes;
    // 是否撤销
    let isRender = false;

    // 拖动积木包含目标积木
    if (closest.getSurroundParent() == local) {
      // 拖动积木在最外层
      // 找到被包含的所有积木是否能放入自己内部
      localTypes = getChildrenTypes(closest);
      // 比较local的类型与 parentsLimitTypes 是否有冲突
      isRender = localTypes.some(
        block => {
          const type = local.type;
          return Blockly.BlockSvg.LIMIT_BLOCKOBJ[type]? Blockly.BlockSvg.LIMIT_BLOCKOBJ[type].includes(block) : false
        });
      // 如果有父层，找到所有父层是否能包含自己
      if (local.getSurroundParent() != null) {
        if (!isRender){
          localTypes = getChildrenTypes(local);
          // 内层没有问题的情况下，判断父层
          parentsLimitTypes = this.getParentsLimitTypes(local);
          isRender = parentsLimitTypes.some(block => localTypes.includes(block));
        }
      }
    } else {
      localTypes = getChildrenTypes(local);
      // 不包含内部积木
      parentsLimitTypes = this.getParentsLimitTypes(local);
      isRender = parentsLimitTypes.some(block => localTypes.includes(block));
    }

    console.log("localTypes ==>", localTypes) 
    console.log("parentsLimitTypes ==>", parentsLimitTypes) 
    
    if (isRender) {
      console.log(' ==> 撤销');
      setTimeout(function() {
        Blockly.mainWorkspace.undo(false)
      });
    }
    
    /* ********************************************* */
  }
};


/**
 * 获取符合限制块类型的子积木
 * 从给定的 `block` 及其所有子积木中，遍历并收集符合限制块类型 `LIMIT_BLOCK` 的子积木到 `childLimitTypes` 数组中。
 *
 * @param {Blockly.BlockSvg} block - 起始积木
 * @return {Array} 符合限制块类型的子积木的数组
 */

const getChildrenTypes = (block) => {
  const childLimitTypes = [];

  /**
   * 递归获取子积木
   * @param {Blockly.BlockSvg} childBlock - 当前子积木
   */
  function getChild(childBlock) {
    if (Blockly.BlockSvg.LIMIT_BLOCK.includes(childBlock.type)) {
      childLimitTypes.push(childBlock.type);
    }
    for (const nestedChildBlock of childBlock.childBlocks_) {
      getChild(nestedChildBlock);
    }
  }

  // 开始遍历获取子积木
  getChild(block);

  return [...new Set(childLimitTypes)];
};

// 新增方法
// 条件嵌套向上寻找有几层
Blockly.InsertionMarkerManager.prototype.termNums = function (block, num = 1) {
  const parent = block.getSurroundParent()
  if (parent != null && parent.outputShape_ == 1 && (parent.type == "operator_or" || parent.type == "operator_and")) {
    num += 1
    return this.termNums(parent, num)
  } 
  return num
}

// 新增方法
// 条件嵌套自身寻找
Blockly.InsertionMarkerManager.prototype.localTermNums = function (block, num = 1) {
  function each(array, floor = 0 ) {
    array.forEach(element => {
      // 判断深度
      num = Math.max(num,floor)
      if(element.outputShape_ == 1 && (element.type == "operator_or" || element.type == "operator_and")) {
        if(element.childBlocks_.length>0) {
          each(element.childBlocks_, floor+1)
        } else {
          num = Math.max(num,floor+1)
        }
      }
    });
  }
  if (block.childBlocks_.length > 0) {
    each(block.childBlocks_, 1)
  }
  return num
}

/**
 * 收集限制块类型
 * 从传入积木开始沿着外层包裹积木向上遍历，获取每一层外层包裹积木定义的限制块类型。
 * 返回一个数组，包含所有涉及的限制块类型，并确保没有重复项。
 *
 * @param {Blockly.BlockSvg} localBlock - 传入积木
 * @return {Array} 包含限制块类型的数组
 */
Blockly.InsertionMarkerManager.prototype.getParentsLimitTypes = function (localBlock) {
  let typeArr = [];

  let currentBlock = localBlock;
  while (currentBlock) {
    const surroundParent = currentBlock.getSurroundParent();
    if (surroundParent) {
      typeArr = typeArr.concat(Blockly.BlockSvg.LIMIT_BLOCKOBJ[surroundParent.type]);
      currentBlock = surroundParent;
    } else {
      currentBlock = null;
    }
  }

  return [...new Set(typeArr)];
};

// Blockly.FieldImage.prototype.EDITABLE = true;

Blockly.FieldImage.prototype.onMouseDown_ = function() {
  return
  // Blockly.FieldImage.prototype.EDITABLE = true;
};

/**
 * Handle a mouse down event on a field.
 * @param {!Event} e Mouse down event.
 * @private
 */
Blockly.Field.prototype.onMouseDown_ = function(e) {
  // console.log("🚀 ~ file: blockThemeDraw.js ~ line 667 ~ Field", 'Field')
  if (!this.sourceBlock_ || !this.sourceBlock_.workspace) {
    return;
  }
  var gesture = this.sourceBlock_.workspace.getGesture(e);
  if (gesture) {
    gesture.setStartField(this);
  }
  this.useTouchInteraction_ = Blockly.Touch.getTouchIdentifierFromEvent(event) !== 'mouse';
  Blockly.Field.prototype.EDITABLE = true;
  Blockly.FieldImage.prototype.EDITABLE = false;
};


Blockly.FieldImage.prototype.init = function() {
  if (this.fieldGroup_) {
    // Image has already been initialized once.
    return;
  }
  // Build the DOM.
  /** @type {SVGElement} */
  this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
  if (!this.visible_) {
    this.fieldGroup_.style.display = 'none';
  }
  /** @type {SVGElement} */
  if (this.isCustomize == null) {
    this.imageElement_ = Blockly.utils.createSvgElement(
      'image',
      {
        'height': this.height_ + 'px',
        'width': this.width_ + 'px'
      },
      this.fieldGroup_);
  } else {
    this.imageElement_ = Blockly.utils.createSvgElement(
      'image',
      {
        'height': this.isCustomize.imgHeight + 'px',
        'width': this.isCustomize.imgWidth + 'px'
      },
      this.fieldGroup_);
  }
      this.setValue(this.src_);
  this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);

  // Configure the field to be transparent with respect to tooltips.
  this.setTooltip(this.sourceBlock_);
  Blockly.Tooltip.bindMouseEvents(this.imageElement_);
  this.mouseDownWrapper_ = Blockly.bindEventWithChecks_(
      this.getClickTarget_(), 'mousedown', this, this.onMouseDown_);
};

/**
 * Dispose of all DOM objects belonging to this text.
 */
Blockly.FieldImage.prototype.dispose = function() {
  if (this.mouseDownWrapper_) {
    Blockly.unbindEvent_(this.mouseDownWrapper_);
    this.mouseDownWrapper_ = null;
  }
  this.sourceBlock_ = null;
  // goog.dom.removeNode(this.fieldGroup_);
  this.fieldGroup_ = null;
  this.imageElement_ = null;
};

/**
 * Editable fields usually show some sort of UI for the user to change them.
 * @type {boolean}
 * @public
 */

Blockly.Field.prototype.isCurrentlyEditable = function() {
  return Blockly.Field.prototype.EDITABLE  && !!this.sourceBlock_ && this.sourceBlock_.isEditable();
};

Blockly.Gesture.prototype.isImgFieldClick_ = function() {
  return Blockly.FieldImage.prototype.EDITABLE;
};

Blockly.Gesture.prototype.isFieldClick_ = function() {
  var fieldEditable = this.startField_ ?
      this.startField_.isCurrentlyEditable() : false;
      
  return fieldEditable && !this.hasExceededDragRadius_;
};

/**
 * Handle a mouse up or touch end event.
 * @param {!Event} e A mouse up or touch end event.
 * @package
 */
Blockly.Gesture.prototype.handleUp = function(e) {
  this.updateFromEvent_(e);
  Blockly.longStop_();

  if (this.isEnding_) {
    return;
  }
  this.isEnding_ = true;
  // The ordering of these checks is important: drags have higher priority than
  // clicks.  Fields have higher priority than blocks; blocks have higher
  // priority than workspaces.
  // The ordering within drags does not matter, because the three types of
  // dragging are exclusive.
  if (this.isDraggingBubble_) {
    this.bubbleDragger_.endBubbleDrag(e, this.currentDragDeltaXY_);
  } else if (this.isDraggingBlock_) {
    this.blockDragger_.endBlockDrag(e, this.currentDragDeltaXY_);
  } else if (this.isDraggingWorkspace_) {
    this.workspaceDragger_.endDrag(this.currentDragDeltaXY_);
  } else if (this.isBubbleClick_()) {
    // Bubbles are in front of all fields and blocks.
    this.doBubbleClick_();
  } else if (this.isFieldClick_()) {
    this.doFieldClick_();
  } 
  // else if (this.isImgFieldClick_()) {
  //   this.doImgFieldClick_();
  // }  
  else if (this.isBlockClick_()) {
    this.doBlockClick_();
  } else if (this.isWorkspaceClick_()) {
    this.doWorkspaceClick_();
  }

  e.preventDefault();
  e.stopPropagation();

  this.dispose();
};

/**
 * Execute a field click.
 * @private
 */
Blockly.Gesture.prototype.doImgFieldClick_ = function() {
   
  this.bringBlockToFront_();
  var event = new Blockly.Events.Ui(null, 'imgField', null, null);
  event.block = this.startField_
  event.targetBlock_ = this.targetBlock_
  // console.log("🚀 ~ file: gesture.js ~ line 739 ~ this.startField_", this.targetBlock_)
  event.workspaceId = this.creatorWorkspace_.id;
  Blockly.Events.fire(event);
};

/**
 * Install this text on a block.
 */
 Blockly.FieldLabel.prototype.init = function() {
  if (this.textElement_) {
    // Text has already been initialized once.
    return;
  }

  const blackList = ["operator_and", "operator_or"];

  // Build the DOM.
  this.textElement_ = Blockly.utils.createSvgElement('text',
      {
        'class': `blocklyText ${blackList.includes(this.sourceBlock_.type) ? 'blocklyTextColor':''}`,
        'y': Blockly.BlockSvg.FIELD_TOP_PADDING,
        'text-anchor': 'middle',
        'dominant-baseline': 'middle',
        'style': `--color:${blackList.includes(this.sourceBlock_.type) ? '#244D6F':'#fff'};`,
        // 'dy': goog.userAgent.EDGE_OR_IE ? Blockly.Field.IE_TEXT_OFFSET : '0'
        'dy': '0'
      }, null);
  if (this.class_) {
    Blockly.utils.addClass(this.textElement_, this.class_);
  }
  if (!this.visible_) {
    this.textElement_.style.display = 'none';
  }
  this.sourceBlock_.getSvgRoot().appendChild(this.textElement_);

  // Configure the field to be transparent with respect to tooltips.
  this.textElement_.tooltip = this.sourceBlock_;
  Blockly.Tooltip.bindMouseEvents(this.textElement_);
  // Force a render.
  this.render_();
};



/**
 * Dispose of this block.
 * @param {boolean} healStack If true, then try to heal any gap by connecting
 *     the next statement with the previous statement.  Otherwise, dispose of
 *     all children of this block.
 * 处理掉这个块。
 * @param {boolean}healStack 如果为真，则尝试通过连接来修复任何间隙
 *     下一个语句与上一个语句。否则，丢弃
 *     这个块的所有孩子。
 */
// Blockly.Block.prototype.dispose = function(healStack) {
//   if (!this.workspace) {
//     // Already deleted.
//     return;
//   }
//   // Terminate onchange event calls.
//   if (this.onchangeWrapper_) {
//     this.workspace.removeChangeListener(this.onchangeWrapper_);
//   }
//   this.unplug(healStack);
//   if (Blockly.Events.isEnabled()) {
//     Blockly.Events.fire(new Blockly.Events.BlockDelete(this));
//   }
//   Blockly.Events.disable();

//   try {
//     // This block is now at the top of the workspace.
//     // Remove this block from the workspace's list of top-most blocks.
//     if (this.workspace) {
//       this.workspace.removeTopBlock(this);
//       delete this.workspace.blockDB_[this.id];
//       if (!this.workspace.hasOwnProperty('targetWorkspace')) {
//         var toolbox = this.workspace.getToolbox();
//         if(this.type == "control_when") {
//           let hasDelBlock = toolbox.categoryMenu_.categories_[0].contents_.findIndex(item => item.attributes[0].value == "control_when" ) > -1
//           let workSpaceHasTop = this.workspace.topBlocks_.findIndex(item => item.type == "control_when") > -1
//           if(hasDelBlock && workSpaceHasTop) {
//             let block = toolbox.categoryMenu_.categories_[0].contents_.shift()
//             toolbox.setDelBlocks(block)
//             toolbox.setSelectedItem(toolbox.selectedItem_)
//           } else if (!hasDelBlock && !workSpaceHasTop) {
//             toolbox.categoryMenu_.categories_[0].contents_.unshift(toolbox.delBlocks)
//             toolbox.setDelBlocks(null)
//             toolbox.setSelectedItem(toolbox.selectedItem_) 
//           }
//         }
//       }
//       // Remove from block database.
//       this.workspace = null;
//     }

//     // Just deleting this block from the DOM would result in a memory leak as
//     // well as corruption of the connection database.  Therefore we must
//     // methodically step through the blocks and carefully disassemble them.

//     if (Blockly.selected == this) {
//       Blockly.selected = null;
//     }

//     // First, dispose of all my children.
//     for (var i = this.childBlocks_.length - 1; i >= 0; i--) {
//       this.childBlocks_[i].dispose(false);
//     }
//     // Then dispose of myself.
//     // Dispose of all inputs and their fields.
//     for (var i = 0, input; input = this.inputList[i]; i++) {
//       input.dispose();
//     }
//     this.inputList.length = 0;
//     // Dispose of any remaining connections (next/previous/output).
//     var connections = this.getConnections_(true);
//     for (var i = 0; i < connections.length; i++) {
//       var connection = connections[i];
//       if (connection.isConnected()) {
//         connection.disconnect();
//       }
//       connections[i].dispose();
//     }
//   } finally {
//     Blockly.Events.enable();
//   }
// };

Blockly.Block.prototype.dispose = function(healStack) {
  if (!this.workspace) {
    // Already deleted.
    return;
  }
  // Terminate onchange event calls.
  if (this.onchangeWrapper_) {
    this.workspace.removeChangeListener(this.onchangeWrapper_);
  }
  this.unplug(healStack);
  if (Blockly.Events.isEnabled()) {
    Blockly.Events.fire(new Blockly.Events.BlockDelete(this));
  }
  Blockly.Events.disable();

  try {
    // This block is now at the top of the workspace.
    // Remove this block from the workspace's list of top-most blocks.
    if (this.workspace) {
      this.workspace.removeTopBlock(this);
      // Remove from block database.
      delete this.workspace.blockDB_[this.id];
      this.workspace = null;
    }

    // Just deleting this block from the DOM would result in a memory leak as
    // well as corruption of the connection database.  Therefore we must
    // methodically step through the blocks and carefully disassemble them.

    if (Blockly.selected == this) {
      Blockly.selected = null;
    }

    // First, dispose of all my children.
     // eslint-disable-next-line
    for (var i = this.childBlocks_.length - 1; i >= 0; i--) {
      this.childBlocks_[i].dispose(false);
    }
    // Then dispose of myself.
    // Dispose of all inputs and their fields.
     // eslint-disable-next-line
    for (var i = 0, input; input = this.inputList[i]; i++) {
      input.dispose();
    }
    this.inputList.length = 0;
    // Dispose of any remaining connections (next/previous/output).
    var connections = this.getConnections_(true);
    for (var i = 0; i < connections.length; i++) {
      var connection = connections[i];
      if (connection.isConnected()) {
        connection.disconnect();
      }
      connections[i].dispose();
    }
  } finally {
    Blockly.Events.enable();
  }
};

/**
 * Update the cursor (and possibly the trash can lid) to reflect whether the
 * dragging block would be deleted if released immediately.
 * @param {boolean} isOutside True if the cursor is outside of the blocks workspace
 * 更新光标（可能还有垃圾桶盖）以反映是否
 * 如果立即释放拖动块将被删除。
 * @param {boolean} isOutside 如果光标在块工作区之外，则为真
 * @private
 */
Blockly.BlockDragger.prototype.updateCursorDuringBlockDrag_ = function(isOutside) {
  this.wouldDeleteBlock_ = this.draggedConnectionManager_.wouldDeleteBlock();
  var trashcan = this.workspace_.trashcan;
 
  if (this.wouldDeleteBlock_) {
    if (this.draggingBlock_.type == 'control_when') {
      this.draggingBlock_.setDeleteStyle(false);
      if (trashcan) {
        trashcan.setOpen_(false);
      }
    } else {
      this.draggingBlock_.setDeleteStyle(true);
      if (this.deleteArea_ == Blockly.DELETE_AREA_TRASH && trashcan) {
        trashcan.setOpen_(true);
      }
    }
  } else {
    this.draggingBlock_.setDeleteStyle(false);
    if (trashcan) {
      trashcan.setOpen_(false);
    }
  }

  if (isOutside) {
    // Let mouse events through to GUI
    this.draggingBlock_.setMouseThroughStyle(true);
  } else {
    this.draggingBlock_.setMouseThroughStyle(false);
  }
};

/**
 * Shut the trash can and, if necessary, delete the dragging block.
 * Should be called at the end of a block drag.
 * @return {boolean} whether the block was deleted.
 * 关闭垃圾桶，如有必要，删除拖动块。
 * 应该在块拖动结束时调用。
 * @return {boolean} 块是否被删除
 * @private
 */
Blockly.BlockDragger.prototype.maybeDeleteBlock_ = function() {
  var trashcan = this.workspace_.trashcan;
  if (this.wouldDeleteBlock_) {
    if (this.draggingBlock_.type=='control_when') {
      setTimeout(()=>{
        Blockly.mainWorkspace.undo(false)
      });
      return false
    }
    if (trashcan) {
      throttle(trashcan.close, 100, trashcan);
    }
    // Fire a move event, so we know where to go back to for an undo.
    this.fireMoveEvent_();
    this.draggingBlock_.dispose(false, true);
  } else if (trashcan) {
    // Make sure the trash can is closed.
    trashcan.close();
  }
  return this.wouldDeleteBlock_;
};


Blockly.Gesture.prototype.handleRightClick = function (e) {
  if (Blockly.BlockSvg.NO_CONTEXT_MENU_DELETE.includes(this.targetBlock_?.type)) {
    return
  }

  if (this.targetBlock_) {
    this.bringBlockToFront_();
    Blockly.hideChaff(this.flyout_);
    this.targetBlock_.showContextMenu_(e);
  } else if (this.startBubble_) {
    this.startBubble_.showContextMenu_(e);
  } else if (this.startWorkspace_ && !this.flyout_) {
    Blockly.hideChaff();
    this.startWorkspace_.showContextMenu_(e);
  }

  // TODO: Handle right-click on a bubble.
  e.preventDefault();
  e.stopPropagation();

  this.dispose();
};